//== GAIA ============================================================================

//== BEGINNING OF CODE ===============================================================

#include "sagittarius/core/SagittariusCore.h"

//#include "/home/odin/myhome/downloads/llvm/3.1-release/gnubuild/include/llvm/Support/DataTypes.h"
//#include "C:/Users/odin/Downloads/llvm/3.1-release/xllvm-3.1-build/include/llvm/Support/DataTypes.h"
#include "llvm/Support/DataTypes.h"
#include "llvm/DerivedTypes.h"
#include "llvm/Module.h"
//#include "llvm/ModuleProvider.h"
#include "llvm/ExecutionEngine/ExecutionEngine.h"
#include "llvm/ExecutionEngine/JIT.h"
//#include "llvm/ExecutionEngine/Interpreter.h"
#include "llvm/ExecutionEngine/GenericValue.h"
#include "llvm/LLVMContext.h"
//#include "llvm/Type.h"
//#include "llvm/Target/TargetData.h"
//#include "llvm/Transforms/Scalar.h"
#include "llvm/IRBuilder.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Analysis/Verifier.h" //for llvm::verifyFunction

#include <iostream>
#include <stdio.h>

//static llvm::Module *M;
//static llvm::ExecutionEngine *EE;

llvm::Module *M;
llvm::ExecutionEngine* EE;
llvm::IRBuilder<>* builder;

llvm::Function *f, *g;

llvm::Instruction* inst;

llvm::Function* fprintHelloWorld;

extern "C" void printHelloWorld(int* i) {
    printf("Hello world, main code %d\n", *i);
}

llvm::Value* get_fn_arg(llvm::Function* f, uint64 n) {
    llvm::Function::arg_iterator a;
    for(a=f->arg_begin(); n; --n, ++f);
    return a;
}

int main(int argc, char* argv[])
{
    llvm::InitializeNativeTarget();
    llvm::LLVMContext &Context = llvm::getGlobalContext();
    // Make the module, which holds all the code.
    M = new llvm::Module("modula", Context);
    // create ee
    EE = llvm::ExecutionEngine::create(M, false);
    // create builder
    builder = new llvm::IRBuilder<>(llvm::getGlobalContext());
    
    llvm::Type* i32_tp = llvm::IntegerType::get(llvm::getGlobalContext(), 32);
    llvm::Type* i32p_tp = llvm::PointerType::get(i32_tp,0);
    
    // set up the function hook
    fprintHelloWorld = llvm::cast<llvm::Function>(M->getOrInsertFunction("funHelloWorld", llvm::Type::getVoidTy(llvm::getGlobalContext()), i32p_tp, NULL));
    EE->addGlobalMapping(fprintHelloWorld, (void*)(&printHelloWorld));
    
    f = llvm::cast<llvm::Function>(M->getOrInsertFunction("f", llvm::Type::getVoidTy(llvm::getGlobalContext()), i32_tp, NULL));
    llvm::BasicBlock *BBf = llvm::BasicBlock::Create(llvm::getGlobalContext(), "entry", f);
    g = llvm::cast<llvm::Function>(M->getOrInsertFunction("g", llvm::Type::getVoidTy(llvm::getGlobalContext()), i32p_tp, NULL));
    llvm::BasicBlock *BBg = llvm::BasicBlock::Create(llvm::getGlobalContext(), "entry", g);
    
    M->dump();
    
    printf("x1\n");
    
    builder->SetInsertPoint(BBf);
    llvm::Value* ip = builder->CreateAlloca(i32_tp, NULL, "int_alloca");
    builder->CreateCall(g, ip);
    inst = builder->CreateRetVoid();
    llvm::verifyFunction(*f);
    
    printf("x2\n");
    
    builder->SetInsertPoint(BBg);
    builder->CreateCall(fprintHelloWorld, get_fn_arg(g,0));
    inst = builder->CreateRetVoid();
    llvm::verifyFunction(*g);
    
    printf("x3\n");
    
    M->dump();
    
    // run the function
    std::vector<llvm::GenericValue> fargs;
    fargs.push_back(llvm::GenericValue(10));
    EE->runFunction(f, fargs);
    
    delete EE;
    delete builder;
    //delete M; //do we need this? I think LLVM takes care of it
    //llvm::llvm_shutdown();
    return 0;
}
